// -*- Mode: C++ -*-
class Mutator {
public:
  virtual ~Mutator() { }
  virtual void Mutate(SegmentMap *table,
		      const SegmentMap *source_table,
		      MTRandom *rand) const = 0;
};

class Change {
public:
  enum Kind {
    MODIFY = 1,     // Mutate a certain range w/ random or supplied data
    ADD = 2,        // Insert random or supplied data
    DELRANGE = 3,     // Delete a specified range of data
    COPY = 4,       // Copy from one region, inserting elsewhere
    MOVE = 5,       // Copy then delete copied-from range
    COPYOVER = 6    // Copy then delete copied-to range

    // ADD, DELRANGE, and COPY change the file size
    // MODIFY, MOVE, COPYOVER preserve the file size
  };

  // Constructor for modify, add, delete.
  Change(Kind kind0, xoff_t size0, xoff_t addr1_0)
    : kind(kind0),
      size(size0),
      addr1(addr1_0),
      addr2(0),
      insert(NULL) {
    CHECK(kind != MOVE && kind != COPY && kind != COPYOVER);
  }

  // Constructor for modify, add w/ provided data.
  Change(Kind kind0, xoff_t size0, xoff_t addr1_0, Segment *insert0)
    : kind(kind0),
      size(size0),
      addr1(addr1_0),
      addr2(0),
      insert(insert0) {
    CHECK(kind != MOVE && kind != COPY && kind != COPYOVER);
  }

  // Constructor for move, copy, overwrite
  Change(Kind kind0, xoff_t size0, xoff_t addr1_0, xoff_t addr2_0)
    : kind(kind0),
      size(size0),
      addr1(addr1_0),
      addr2(addr2_0),
      insert(NULL) {
    CHECK(kind == MOVE || kind == COPY || kind == COPYOVER);
  }

  Kind kind;
  xoff_t size;
  xoff_t addr1;
  xoff_t addr2;
  Segment *insert;  // For modify and/or add
};

typedef list<Change> ChangeList;
typedef typename ChangeList::const_iterator ConstChangeListIterator;
typedef typename ChangeList::iterator ChangeListIterator;

class ChangeListMutator : public Mutator {
public:
  ChangeListMutator(const ChangeList &cl)
    : cl_(cl) { }

  ChangeListMutator() { }

  void Mutate(SegmentMap *table,
	      const SegmentMap *source_table,
	      MTRandom *rand) const {
    // The speed of processing gigabytes of data is so slow compared with
    // these table-copy operations, no attempt to make this fast.
    SegmentMap tmp;

    for (ConstChangeListIterator iter(cl_.begin());
	 iter != cl_.end(); ++iter) {
      const Change &ch = *iter;
      tmp.clear();
      Mutate(ch, &tmp, source_table, rand);
      tmp.swap(*table);
      source_table = table;
    }
  }

  static void Mutate(const Change &ch,
		     SegmentMap *table,
		     const SegmentMap *source_table,
		     MTRandom *rand) {
    switch (ch.kind) {
    case Change::ADD:
      AddChange(ch, table, source_table, rand);
      break;
    case Change::MODIFY:
      ModifyChange(ch, table, source_table, rand);
      break;
    case Change::DELRANGE:
      DeleteChange(ch, table, source_table, rand);
      break;
    case Change::COPY:
      CopyChange(ch, table, source_table, rand);
      break;
    case Change::MOVE:
      MoveChange(ch, table, source_table, rand);
      break;
    case Change::COPYOVER:
      OverwriteChange(ch, table, source_table, rand);
      break;
    }
  }

  static void ModifyChange(const Change &ch,
			   SegmentMap *table,
			   const SegmentMap *source_table,
			   MTRandom *rand) {
    xoff_t m_start = ch.addr1;
    xoff_t m_end = m_start + ch.size;
    xoff_t i_start = 0;
    xoff_t i_end = 0;

    for (ConstSegmentMapIterator iter(source_table->begin());
	 iter != source_table->end();
	 ++iter) {
      const Segment &seg = iter->second;
      i_start = iter->first;
      i_end = i_start + seg.Size();

      if (i_end <= m_start || i_start >= m_end) {
	table->insert(table->end(), make_pair(i_start, seg));
	continue;
      }

      if (i_start < m_start) {
	table->insert(table->end(),
		      make_pair(i_start,
				seg.Subseg(0, m_start - i_start)));
      }

      // Insert the entire segment, even though it may extend into later
      // segments.  This condition avoids inserting it during later
      // segments.
      if (m_start >= i_start) {
	if (ch.insert != NULL) {
	  table->insert(table->end(), make_pair(m_start, *ch.insert));
	} else {
	  Segment part(m_end - m_start, rand);
	  table->insert(table->end(), make_pair(m_start, part));
	}
      }

      if (i_end > m_end) {
	table->insert(table->end(),
		      make_pair(m_end,
				seg.Subseg(m_end - i_start, i_end - m_end)));
      }
    }

    // This check verifies that the modify does not extend past the
    // source_table EOF.
    CHECK_LE(m_end, i_end);
  }

  static void AddChange(const Change &ch,
			SegmentMap *table,
			const SegmentMap *source_table,
			MTRandom *rand) {
    xoff_t m_start = ch.addr1;
    xoff_t i_start = 0;
    xoff_t i_end = 0;

    for (ConstSegmentMapIterator iter(source_table->begin());
	 iter != source_table->end();
	 ++iter) {
      const Segment &seg = iter->second;
      i_start = iter->first;
      i_end = i_start + seg.Size();

      if (i_end <= m_start) {
	table->insert(table->end(), make_pair(i_start, seg));
	continue;
      }

      if (i_start > m_start) {
	table->insert(table->end(), make_pair(i_start + ch.size, seg));
	continue;
      }

      if (i_start < m_start) {
	table->insert(table->end(),
		      make_pair(i_start,
				seg.Subseg(0, m_start - i_start)));
      }

      if (ch.insert != NULL) {
	table->insert(table->end(), make_pair(m_start, *ch.insert));
      } else {
	Segment addseg(ch.size, rand);
	table->insert(table->end(), make_pair(m_start, addseg));
      }

      if (m_start < i_end) {
	table->insert(table->end(),
		      make_pair(m_start + ch.size,
				seg.Subseg(m_start - i_start,
					   i_end - m_start)));
      }
    }

    CHECK_LE(m_start, i_end);

    // Special case for add at end-of-input.
    if (m_start == i_end) {
      Segment addseg(ch.size, rand);
      table->insert(table->end(), make_pair(m_start, addseg));
    }
  }

  static void DeleteChange(const Change &ch,
			   SegmentMap *table,
			   const SegmentMap *source_table,
			   MTRandom *rand) {
    xoff_t m_start = ch.addr1;
    xoff_t m_end = m_start + ch.size;
    xoff_t i_start = 0;
    xoff_t i_end = 0;

    for (ConstSegmentMapIterator iter(source_table->begin());
	 iter != source_table->end();
	 ++iter) {
      const Segment &seg = iter->second;
      i_start = iter->first;
      i_end = i_start + seg.Size();

      if (i_end <= m_start) {
	table->insert(table->end(), make_pair(i_start, seg));
	continue;
      }

      if (i_start >= m_end) {
	table->insert(table->end(), make_pair(i_start - ch.size, seg));
	continue;
      }

      if (i_start < m_start) {
	table->insert(table->end(),
		      make_pair(i_start,
				seg.Subseg(0, m_start - i_start)));
      }

      if (i_end > m_end) {
	table->insert(table->end(),
		      make_pair(m_end - ch.size,
				seg.Subseg(m_end - i_start, i_end - m_end)));
      }
    }

    CHECK_LT(m_start, i_end);
    CHECK_LE(m_end, i_end);
  }

  // A move is a copy followed by delete of the copied-from range.
  static void MoveChange(const Change &ch,
			 SegmentMap *table,
			 const SegmentMap *source_table,
			 MTRandom *rand) {
    SegmentMap tmp;
    CHECK_NE(ch.addr1, ch.addr2);
    CopyChange(ch, &tmp, source_table, rand);
    Change d(Change::DELRANGE, ch.size,
	     ch.addr1 < ch.addr2 ? ch.addr1 : ch.addr1 + ch.size);
    DeleteChange(d, table, &tmp, rand);
  }

  // An overwrite is a copy followed by a delete of the copied-to range.
  static void OverwriteChange(const Change &ch,
			      SegmentMap *table,
			      const SegmentMap *source_table,
			      MTRandom *rand) {
    SegmentMap tmp;
    CHECK_NE(ch.addr1, ch.addr2);
    CopyChange(ch, &tmp, source_table, rand);
    Change d(Change::DELRANGE, ch.size, ch.addr2 + ch.size);
    DeleteChange(d, table, &tmp, rand);
  }

  static void CopyChange(const Change &ch,
			 SegmentMap *table,
			 const SegmentMap *source_table,
			 MTRandom *ignore) {
    xoff_t m_start = ch.addr2;
    xoff_t c_start = ch.addr1;
    xoff_t i_start = 0;
    xoff_t i_end = 0;

    // Like AddChange() with AppendCopy instead of a random segment.
    for (ConstSegmentMapIterator iter(source_table->begin());
	 iter != source_table->end();
	 ++iter) {
      const Segment &seg = iter->second;
      i_start = iter->first;
      i_end = i_start + seg.Size();

      if (i_end <= m_start) {
	table->insert(table->end(), make_pair(i_start, seg));
	continue;
      }

      if (i_start > m_start) {
	table->insert(table->end(), make_pair(i_start + ch.size, seg));
	continue;
      }

      if (i_start < m_start) {
	table->insert(table->end(),
		      make_pair(i_start,
				seg.Subseg(0, m_start - i_start)));
      }

      AppendCopy(table, source_table, c_start, m_start, ch.size);

      if (m_start < i_end) {
	table->insert(table->end(),
		      make_pair(m_start + ch.size,
				seg.Subseg(m_start - i_start, i_end - m_start)));
      }
    }

    CHECK_LE(m_start, i_end);

    // Special case for copy to end-of-input.
    if (m_start == i_end) {
      AppendCopy(table, source_table, c_start, m_start, ch.size);
    }
  }

  static void AppendCopy(SegmentMap *table,
			 const SegmentMap *source_table,
			 xoff_t copy_offset,
			 xoff_t append_offset,
			 xoff_t length) {
    ConstSegmentMapIterator pos(source_table->upper_bound(copy_offset));
    --pos;
    xoff_t got = 0;

    while (got < length) {
      size_t seg_offset = copy_offset - pos->first;
      size_t advance = min(pos->second.Size() - seg_offset,
			   (size_t)(length - got));

      table->insert(table->end(),
		    make_pair(append_offset,
			      pos->second.Subseg(seg_offset,
						 advance)));

      got += advance;
      copy_offset += advance;
      append_offset += advance;
      ++pos;
    }
  }

  ChangeList* Changes() {
    return &cl_;
  }

  const ChangeList* Changes() const {
    return &cl_;
  }

private:
  ChangeList cl_;
};

class Modify1stByte : public Mutator {
public:
  void Mutate(SegmentMap *table,
	      const SegmentMap *source_table,
	      MTRandom *rand) const {
    ChangeListMutator::Mutate(Change(Change::MODIFY, 1, 0),
			      table, source_table, rand);
  }
};
